package org.dreamwork.cai.jmx;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.dreamwork.cai.ServiceNotFoundException;
import org.dreamwork.cai.core.IRemoteServices;
import org.dreamwork.cai.io.IRemoteServiceStub;
import org.dreamwork.cai.core.RemoteServiceFactory;
import org.dreamwork.cai.jmx.beaninfo.Frame;
import org.dreamwork.cai.proxy.IServiceResolver;
import org.dreamwork.cai.srf.MethodInfo;
import org.dreamwork.cai.srf.ServiceRouteFrame;
import org.dreamwork.cai.srf.ServiceRouteItem;
import org.dreamwork.cai.srf.ServiceRouteTable;
import org.dreamwork.cai.tools.Misc;
import org.dreamwork.gson.DateTranslator;

import javax.management.ObjectName;
import java.util.*;

public class CrossApplicationInvocation implements CrossApplicationInvocationMXBean {
    private final Object LOCKER = new byte[0];
    private final ObjectName oName;

    private IServiceResolver resolver;
    private Frame[] frames;

    private static final String JMX_NAME = "CrossApplicationInvocation";

    public CrossApplicationInvocation () {
        oName = Misc.registerJMXService (JMX_NAME, this);
    }

    public void setResolver (IServiceResolver resolver) {
        this.resolver = resolver;
    }

    public void dispose () {
        if (oName != null) {
            Misc.unregisterJMXService (oName);
        }
    }

    @Override
    public Frame[] getRouteTable () {
        synchronized (LOCKER) {
            if (frames == null) {
                ServiceRouteTable srt = resolver.get (ServiceRouteTable.class);
                Gson g = new Gson ();
                String s = g.toJson (srt.frames.values ());

                frames = g.fromJson (s, Frame[].class);
                System.out.println (Arrays.toString (frames));
            }
            return frames;
        }
    }

    /*
    @Override
    public Set<?> getRouteTable () {
        Set<Object> set = new HashSet<> ();
        ServiceRouteTable table = resolver.get (ServiceRouteTable.class);
        for (ServiceRouteFrame srf : table.frames.values ()) {
            Map<String, Object> frame = new HashMap<> ();
            frame.put ("namespace", srf.namespace);
            frame.put ("host", srf.host);
            if (!StringUtil.isEmpty (srf.description))
                frame.put ("description", srf.description);
            frame.put ("port", srf.port);
            frame.put ("metric", srf.metric);
            frame.put ("update_time", new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss").format (srf.timestamp));
            if ("127.0.0.1".equals (srf.host)) {
                frame.put ("local", true);
            }

            if (!srf.items.isEmpty ()) {
                Set<Object> items = new HashSet<> ();
                for (ServiceRouteItem sri : srf.items.values ()) {
                    Map<String, Object> item = new HashMap<> ();
                    item.put ("name", sri.name);
                    item.put ("service_interfaces", Arrays.toString (sri.types));

                    if (!sri.methods.isEmpty ()) {
                        Set<Object> methods = new HashSet<> ();
                        for (MethodInfo mi : sri.methods) {
                            Map<String, Object> info = new HashMap<> ();
                            info.put ("name", mi.name);
                            info.put ("type", mi.type);
                            info.put ("uuid", mi.uuid);
                            if (!StringUtil.isEmpty (mi.description))
                                info.put ("description", mi.description);

                            if (!mi.parameters.isEmpty ()) {
                                String[] params = new String[mi.parameters.size ()];
                                for (int i = 0; i < params.length; i ++) {
                                    params[i] = mi.parameters.get (i).type;
                                }
                                info.put ("parameters", Arrays.toString (params));
                            }

                            methods.add (info);
                        }

                        item.put ("methods", methods);
                    }

                    items.add (item);
                }
                frame.put ("items", items);
            }

            set.add (frame);
        }
        return set;
    }
*/

    @Override
    public String remoteCall (String namespace, String typeName, String uuid, String... args) {
        try {
            Class<?> type = Class.forName (typeName);
            IRemoteServices service = resolver.get (IRemoteServices.class);
            service.lookup (namespace, type);

            ServiceRouteTable table = resolver.get (ServiceRouteTable.class);
            ServiceRouteFrame srf = table.get (namespace);
            for (ServiceRouteItem sri : srf.items.values ()) {
                if (Arrays.asList (sri.types).contains (typeName)) {
                    MethodInfo mi = sri.get (uuid);
                    if (mi != null) {
                        IRemoteServiceStub stub = RemoteServiceFactory.getRemoteService ();
                        Object o = stub.invoke (sri, mi, (Object[]) args);
                        if (o == null) {
                            return null;
                        }

                        type = o.getClass ();
                        if (type.isPrimitive ()) {
                            return String.valueOf (o);
                        } else if (Number.class.isAssignableFrom (type)) {
                            return String.valueOf (o);
                        } else if (CharSequence.class.isAssignableFrom (type)) {
                            return o.toString ();
                        } else {
                            GsonBuilder builder = new GsonBuilder ();
                            builder.setPrettyPrinting ();
                            builder.registerTypeAdapter (java.util.Date.class, new DateTranslator.UtilDateTranslator ())
                                    .registerTypeAdapter (java.sql.Date.class, new DateTranslator.SqlDateTranslator ())
                                    .registerTypeAdapter (java.sql.Timestamp.class, new DateTranslator.TimestampTranslator ());
                            return builder.create ().toJson (o);
                        }
                    }
                }
            }

            throw new ServiceNotFoundException ("service " + namespace + "/" + typeName + "/" + uuid + " not found!");
        } catch (ServiceNotFoundException ex) {
            throw new RuntimeException (ex);
        } catch (Exception ex) {
            ex.printStackTrace ();
        }
        return null;
    }
}